其他
什么时候需要序列化和反序列化?
戳这里,加关注哦~
什么时候需要序列化和反序列化
什么是序列化和反序列化
怎么实现序列化
“ Serializable 接口
Serializable 接口是 Java 提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作。使用 Serializable 来实现的对象的序列化相当简单,只需要在类的生命中指定一个类似相面的标识即可自动实现默认的序列化过程。 这种方式是 Java 提供的一种序列化方式,过程非常简单,甚至有些开发人员都不需要声明 serialVersionUID 也可以完成这个过程,但 serialVersionUID 到底需不需要指定呢? 需要!Java API 既然提供了这个 serialVersionUID,那么它必定是有用的。这个 serialVersionUID 是用来辅助序列化和反序列化过程的,原则上序列化后的数据中的 serialVersionUID 只有和当前类的 serialVersionUID 相同才能够正常地被反序列化。 serialVersionUID 的详细工作过程是这样的:序列化的时候系统会把当前类的 serialVersionUID 写入序列化的二进制文件中,当反序列化的时候系统会检测文件中的 serialVersionUID 是否和当前类的 serialVersionUID 一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化;否则说明当前类和反序列化的类相比发生了某些变化,比如成员变量的数量、类型发生了变化,这个时候是无法正常反序列化的。 一般来说,我们应该手动指定 serialVersionUID 的值,比如 1L,也可以让 IDE 根据当前类的结构自动去生成它的 hash 值,这样序列化和反序列化时两者的 serialVersionUID 是相同的,因此可以正常进行反序列化操作。如果不手动指定 serialVersionUID 的值反序列化时当前类有些改变,比如增加或者删除了某些成员变量,那么系统就会重新计算当前类的 hash 值并把它赋值给 serialVersionUID,这个时候当前类的 serialVersionUID 就和反序列化数据中的 serialVersionUID 不一致,就会造成反序列化失败的结果。 所以,手动指定 serialVersionUID 可以在很大程度上避免反序列化过程的失败。比如当版本升级后,我们可能删除了某个成员变量也可能增加了一些新的成员变量,这个时候我们的反序列化过程依然能够成功,程序仍然能够最大限度地回复数据;相反,如果不指定 serialVersionUID 的话,程序会发生 Crash。 当然,我们还需要考虑一种情况,如果类结构发生了非常规改变,比如修改了类名,修改了成员变量的类型,这个时候尽管 serialVersionUID 验证通过了,但是反序列化过程仍然会失败,因为类的结构有了毁灭性的改变,根本无法从老版本的数据中还原出一个新的类结构的对象。 ”
情况 1:若类仅仅实现了 Serializable 接口
情况 2:若类不仅实现了 Serializable 接口,并且还定义了 readObject(ObjectInputStream in) 和 writeObject(ObjectOutputSteam out)。
情况 3:若类实现了 Externalnalizable 接口,且类必须实现 readExternal(ObjectInput in) 和 writeExternal(ObjectOutput out) 方法。
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer.
*/
// 实际元素被transient修饰,默认不会进行序列化
private transient Object[] elementData;
.....
/**
* Save the state of the <tt>ArrayList</tt> instance to a stream (that
* is, serialize it).
*
* @serialData The length of the array backing the <tt>ArrayList</tt>
* instance is emitted (int), followed by all of its elements
* (each an <tt>Object</tt>) in the proper order.
*/
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out array length
s.writeInt(elementData.length);
// Write out all elements in the proper order.
for (int i=0; i<size; i++)
s.writeObject(elementData[i]);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
/**
* Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,
* deserialize it).
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in array length and allocate array
int arrayLength = s.readInt();
Object[] a = elementData = new Object[arrayLength];
// Read in all elements in the proper order.
for (int i=0; i<size; i++)
a[i] = s.readObject();
}
}
* Serialization's descriptor for classes. It contains the name and
* serialVersionUID of the class. The ObjectStreamClass for a specific class
* loaded in this Java VM can be found/created using the lookup method. 16 */
// 在序列化对象之前会封装一个ObjectStreamClass对象
public class ObjectStreamClass implements Serializable {
/** class-defined writeObject method, or null if none */
private Method writeObjectMethod;
/**
* Creates local class descriptor representing given class.
*/
private ObjectStreamClass(final Class cl) { 36
......
if (serializable) {
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
if (isEnum) {
suid = Long.valueOf(0);
fields = NO_FIELDS;
return null;
}
if (cl.isArray()) {
fields = NO_FIELDS;
return null;
}
suid = getDeclaredSUID(cl);
try {
fields = getSerialFields(cl);
computeFieldOffsets();
} catch (InvalidClassException e) {
serializeEx = deserializeEx = e;
fields = NO_FIELDS;
}
if (externalizable) {
cons = getExternalizableConstructor(cl);
} else {
cons = getSerializableConstructor(cl);
// 其实就是writeObject方法
writeObjectMethod = getPrivateMethod(cl, "writeObject",
new Class[] { ObjectOutputStream.class },
Void.TYPE);
readObjectMethod = getPrivateMethod(cl, "readObject",
new Class[] { ObjectInputStream.class },
Void.TYPE);
readObjectNoDataMethod = getPrivateMethod(
cl, "readObjectNoData", null, Void.TYPE);
hasWriteObjectData = (writeObjectMethod != null);
}
writeReplaceMethod = getInheritableMethod(
cl, "writeReplace", null, Object.class);
readResolveMethod = getInheritableMethod(
cl, "readResolve", null, Object.class);
return null;
}
});
} else {
suid = Long.valueOf(0);
fields = NO_FIELDS;
}
.......107 }
/**
* Returns non-static private method with given signature defined by given
* class, or null if none found. Access checks are disabled on the
* returned method (if any).
*/
private static Method getPrivateMethod(Class cl, String name,
Class[] argTypes,
Class returnType)
{
try {
Method meth = cl.getDeclaredMethod(name, argTypes);
meth.setAccessible(true);
int mods = meth.getModifiers();
return ((meth.getReturnType() == returnType) &&
((mods & Modifier.STATIC) == 0) &&
((mods & Modifier.PRIVATE) != 0)) ? meth : null;
} catch (NoSuchMethodException ex) {
return null;
}
}
/**
* Returns true if represented class is serializable (but not
* externalizable) and defines a conformant writeObject method. Otherwise,
* returns false.
*/
boolean hasWriteObjectMethod() {
return (writeObjectMethod != null);
}
}
public class ObjectOutputStream
extends OutputStream implements ObjectOutput, ObjectStreamConstants
{
/**
* Magic number that is written to the stream header.
*/
final static short STREAM_MAGIC = (short)0xaced;
/**
* Version number that is written to the stream header.
*/
final static short STREAM_VERSION = 5;
public ObjectOutputStream(OutputStream out) throws IOException {
verifySubclass();
bout = new BlockDataOutputStream(out);
handles = new HandleTable(10, (float) 3.00);
subs = new ReplaceTable(10, (float) 3.00);
enableOverride = false;
// 写入头信息
writeStreamHeader();
bout.setBlockDataMode(true);
if (extendedDebugInfo) {
debugInfoStack = new DebugTraceInfoStack();
} else {
debugInfoStack = null;
}
}
protected void writeStreamHeader() throws IOException {
bout.writeShort(STREAM_MAGIC);
bout.writeShort(STREAM_VERSION);
}
/**
* Write the specified object to the ObjectOutputStream. The class of the
* object, the signature of the class, and the values of the non-transient
* and non-static fields of the class and all of its supertypes are
* written. Default serialization for a class can be overridden using the
* writeObject and the readObject methods. Objects referenced by this
* object are written transitively so that a complete equivalent graph of
* objects can be reconstructed by an ObjectInputStream.196 */
public final void writeObject(Object obj) throws IOException {
if (enableOverride) {
writeObjectOverride(obj);
return;
}
try {
writeObject0(obj, false);
} catch (IOException ex) {
if (depth == 0) {
writeFatalException(ex);
}
throw ex;
}
}
/**
* Underlying writeObject/writeUnshared implementation.
*/
private void writeObject0(Object obj, boolean unshared)
throws IOException
{
boolean oldMode = bout.setBlockDataMode(false);
depth++;
try {
// handle previously written and non-replaceable objects
......
// check for replacement object
......241 261
// if object replaced, run through original checks a second time
......279
// remaining cases
if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum) obj, desc, unshared);
} else if (obj instanceof Serializable) {
// 如果不是特殊对象类型,最终会调用该方法
writeOrdinaryObject(obj, desc, unshared);
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
} finally {
depth--;
bout.setBlockDataMode(oldMode);
}
}
private void writeOrdinaryObject(Object obj,
ObjectStreamClass desc,
boolean unshared)
throws IOException
{
if (extendedDebugInfo) {
debugInfoStack.push(
(depth == 1 ? "root " : "") + "object (class \"" +
obj.getClass().getName() + "\", " + obj.toString() + ")");
}
try {
desc.checkSerialize();
bout.writeByte(TC_OBJECT);
writeClassDesc(desc, false);
handles.assign(unshared ? null : obj);
if (desc.isExternalizable() && !desc.isProxy()) {
writeExternalData((Externalizable) obj);
} else {
// 一般情况下会调用该方法
writeSerialData(obj, desc);
}
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
/**
* Writes instance data for each serializable class of given object, from
* superclass to subclass.
*/
private void writeSerialData(Object obj, ObjectStreamClass desc)
throws IOException
{
ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
for (int i = 0; i < slots.length; i++) {
ObjectStreamClass slotDesc = slots[i].desc;
// 如果重写了序列化的方法writeObject,则调用对应的方法进行写入,其实就是ObjectStreamClass 中的对应方法,可以得出序列化的第2条规则
if (slotDesc.hasWriteObjectMethod()) {
PutFieldImpl oldPut = curPut;
curPut = null;
if (extendedDebugInfo) {
debugInfoStack.push(
"custom writeObject data (class \"" +
slotDesc.getName() + "\")");
}
SerialCallbackContext oldContext = curContext;
try {
curContext = new SerialCallbackContext(obj, slotDesc);
bout.setBlockDataMode(true);
slotDesc.invokeWriteObject(obj, this);
bout.setBlockDataMode(false);
bout.writeByte(TC_ENDBLOCKDATA);
} finally {
curContext.setUsed();
curContext = oldContext;
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
curPut = oldPut;
} else {
// 未重写调用默认的方法
defaultWriteFields(obj, slotDesc);
}
}
}
总结序列化反序列化的注意点
父类实现了序列化,则子类自动实现了序列化,即子类不需要显式实现 Serializable 接口, 子类构造时会递归调用父类构造。 当父类没有实现序列化,而子类需要实现时,子类需要显式实现 Serializable 接口,并且父类中需要有无参的构造函数。 序列化只对对象的属性进行保存,而不会保存其方法。 当类中的实例变量引用了其他对象,那么在对该类进行序列化时,引用的对象也会被序列化(需要这个引用的对象也实现 Serializable 接口,否则会出现 java.io.NotSerializableException)。 静态成员变量属于类不属于对象,所以不参与序列化过程 用 transient 关键字标记的成员变量不参与序列化过程
最后给大家送下福利,大家可以关注Java核心技术公众号,在后台回复 “福利”可以获取一份我整理的最新Java面试题资料。
最近好文分享
3、连 CopyOnWriteArrayList 都没听过!
5、Spring Boot 中的 RestTemplate 不好用?
……
更多请扫码关注 • Java核心技术
一个分享Java核心技术干货的公众号
点击阅读原文获取面试题~